﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace ttg_internal.Utilities.UsageVisualizer.Other.Controls
{
    /// <summary>
    /// Graph visualizing monitored value history
    /// </summary>
    public class HistoryGraph : Canvas
    {
        /// <summary>
        /// Initializes a new instance
        /// </summary>
        public HistoryGraph()
        {
            for (int i = 0; i < 100; i++)
            {
                _lines[i] = new Line { Stroke = Brushes.LimeGreen, StrokeThickness = 1.5 };
            }

            DataContextChanged += OnDataContextChanged;
            Height = 100;
            Margin = new Thickness(0, 10, 0, 10);
            Width = 1000;
        }

        // Private fields
        private readonly Line[] _lines = new Line[100];
        private PropertyInfo _dataContextProperty;
        private double _currentValue;
        private string _monitoredValuePath;
        private int _x;
        private double _y;

        /// <summary>
        /// Path to the value being monitored
        /// </summary>
        public string MonitoredValuePath
        {
            get { return _monitoredValuePath; }
            set
            {
                if (_monitoredValuePath == value)
                {
                    return;
                }

                _monitoredValuePath = value;
                OnMonitoredValuePathChanged();
            }
        }

        /// <summary>
        /// Performs animation
        /// </summary>
        private void Do2DAnimation()
        {
            // Create a line with X2 and Y2 equivalent, so that it is really just a point.
            Line segment = _lines[_x];
            segment.X1 = _x * 2;
            segment.Y1 = (100 - _y);
            segment.X2 = (_x + 1) * 2;
            segment.Y2 = (100 - _currentValue);

            // Insert the line
            Children.Insert(0, segment);
        }

        private void SetCurrentValue(double currentValue)
        {
            _currentValue = currentValue * 100;
            Do2DAnimation();

            _y = _currentValue;
            ++_x;

            if (_x == 100)
            {
                Line first = _lines[0];

                for (int i = 1; i < _lines.Length; ++i)
                {
                    _lines[i].X1 -= 2;
                    _lines[i].X2 -= 2;
                    _lines[i - 1] = _lines[i];
                }

                _lines[_lines.Length - 1] = first;
                Children.Remove(first);

                --_x;
            }
        }

        /// <summary>
        /// Processes change of DataContext
        /// </summary>
        private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            var dataContext = e.OldValue as INotifyPropertyChanged;

            if (dataContext != null)
            {
                dataContext.PropertyChanged -= OnDataContextPropertyChanged;
            }

            dataContext = e.NewValue as INotifyPropertyChanged;

            if (dataContext != null)
            {
                dataContext.PropertyChanged += OnDataContextPropertyChanged;
                OnMonitoredValuePathChanged();
            }
        }

        private void OnDataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (string.IsNullOrEmpty(MonitoredValuePath))
            {
                return;
            }

            if (e.PropertyName != null && e.PropertyName != MonitoredValuePath)
            {
                return;
            }

            object currentContext = Dispatcher.Invoke(new Func<object>(() => DataContext));
            object value = _dataContextProperty.GetValue(currentContext, null);
            
            Debug.Assert(value is double);
            Dispatcher.Invoke(new Action<double>(SetCurrentValue), value);
        }

        /// <summary>
        /// Called when <see cref="MonitoredValuePath" /> changes
        /// </summary>
        private void OnMonitoredValuePathChanged()
        {
            if (string.IsNullOrEmpty(MonitoredValuePath))
            {
                return;
            }

            Debug.Assert(!MonitoredValuePath.Contains("."));

            _dataContextProperty = DataContext.GetType().GetProperty(MonitoredValuePath);
            Debug.Assert(_dataContextProperty != null);

            if (IsLoaded)
            {
                OnDataContextPropertyChanged(DataContext, new PropertyChangedEventArgs(null));
            }
        }
    }
}


